
namespace Ex
{
    using KeraLua;

    public class IndefiniteVarType : VarType
    {
        private object[] value;

        public object[] Value { get => value; }

        private UsableLuaType[] value_type;

        public UsableLuaType GetValueType(int idx)
        {
            if (idx >= value_type.Length || idx < 0)
            {
                return UsableLuaType.nil;
            }
            if (value_type[idx] == UsableLuaType.any)
            {
                value_type[idx] = Tool.GetCsToLuaType(value[idx]);
            }
            return value_type[idx];
        }

        public int Count
        {
            get { return value.Length; }
        }

        public IndefiniteVarType(params object[] param)
        {
            this.value = param;
            value_type = new UsableLuaType[param.Length];
            for (int i = 0; i < param.Length; i++)
            {
                try
                {
                    value_type[i] = Tool.GetCsToLuaType(value[i]);
                }
                catch (Exception)
                {
                    value_type[i] = UsableLuaType.nil;
                }
            }
        }

        private IndefiniteVarType(object[] param, UsableLuaType[] value_type)
        {
            this.value = param;
            this.value_type = value_type;
        }

        public static IndefiniteVarType FromArray(object[] array) 
        {
            var value_type = new UsableLuaType[array.Length];
            for (int i = 0; i < array.Length; i++)
            {
                try
                {
                    value_type[i] = Tool.GetCsToLuaType(array[i]);
                }
                catch (Exception)
                {
                    value_type[i] = UsableLuaType.nil;
                }
            }
            return new IndefiniteVarType(array, value_type);   
        }


        public static readonly IndefiniteVarType Empty = new(new object[1] { UsableLuaType.nil });

        public bool GetBool(int index)
        {
            if (index >= Count)
            {
                throw new Exception("index out of range");
            }
            try
            {
                return Convert.ToBoolean(value[index]);
            }
            catch {
                throw new Exception($"failed to load {value[index]} as boolean");
            }
        }

        public double GetDouble(int index)
        {
            if (index >= Count)
            {
                throw new Exception("index out of range");
            }
            try
            {
                return Convert.ToDouble(value[index]);
            }
            catch {
                throw new Exception($"failed to load {value[index]} as number");
            }

        }

        public string GetString(int index)
        {
            if (index >= Count)
            {
                throw new Exception("index out of range");
            }
            return value[index] as string ?? throw new Exception($"failed to load {value[index]} as string");

        }

        public IExFunction GetFunction(int index)
        {
            if (index >= Count)
            {
                throw new Exception("index out of range");
            }
            return value[index] as IExFunction ?? throw new Exception($"failed to load {value[index]} as function");

        }

        public IExTable GetTable(int index)
        {
            if (index >= Count)
            {
                throw new Exception("index out of range");
            }
            return value[index] as IExTable ?? throw new Exception($"failed to load {value[index]} as table");
        }

        public ExCsRawData GetData(int index)
        {
            if (index >= Count)
            {
                throw new Exception("index out of range");
            }
            return value[index] as ExCsRawData ?? throw new Exception($"failed to load {value[index]} as csobject");

        }

        public static IndefiniteVarType FetchFromLuaStack(EnvLua env, UsableLuaType[] type)
        {
            lock (env)
            {

                if (type.Length == 0)
                    return Empty;
                object[] param = new object[type.Length];
                int pass_count = env.Current.GetTop();
                if (pass_count == 0)
                    return Empty;
                for (int j = pass_count; j < type.Length; j++)
                {
                    param[j] = UsableLuaType.nil;
                }
                for (int i = pass_count; i > 0; i--)
                {
                    try
                    {
                        if (type[i - 1] != UsableLuaType.any && Tool.GetTypeOnStack(env.Current, i) != type[i - 1])
                        {
                            env.Error(string.Format("Invalid type at {0}: should be {1}", i, type[i - 1]));
                        }
                        param[i - 1] = env.FetchObjectFromLuaStack(type[i - 1]);
                    }
                    catch (Exception exception)
                    {
                        exception.Data["arg_type"] = type[i - 1];
                        exception.Data["arg_order"] = i - 1;
                        throw;
                    }
                }
                var result = new IndefiniteVarType(param)
                {
                    value_type = type
                };
                return result;
            }
        }

        public static IndefiniteVarType FetchFromLuaStack(EnvLua env, int param_count)
        {

            lock (env)
            {
                object[] param = new object[param_count];
                for (int i = param_count; i > 0; i--)
                {
                    try
                    {
                        var value = env.FetchObjectFromLuaStack();
                        if (value != null)
                        {
                            param[i - 1] = value;
                        }
                        else
                        {
                            env.Error(string.Format("Invalid type at {0}", i));
                        }
                    }
                    catch (Exception e)
                    {
                        e.Data["arg_order"] = i - 1;
                        throw;
                    }
                }
                var result = new IndefiniteVarType(param);
                return result;
            }
        }
        

        public override void PushToLuaStack(EnvLua env)
        {
            lock (env)
            {
                for (int i = 0; i < Count; i++)
                {
                    env.PushObjectToLuaStack(value[i], value_type[i]);
                }
            }
        }

        public object Get(int index)
        {
            return value[index];
        }
    }
}